﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace BReusable.StaticReflection
{
    /// <seealso cref="http://blog.imaginarydevelopment.com"/>
    /// <summary>
    /// From BReusable
    /// Avoid this for high performance sections
    /// http://blogs.msdn.com/alexj/archive/2008/03/03/maybe-there-is-more.aspx
    /// Also
    /// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
    /// </summary>
    public static class Walk
    {

        /// <summary>
        /// From
        /// http://davidhayden.com/blog/dave/archive/2006/11/26/IsTypeNullableTypeConverter.aspx
        /// </summary>
        /// <param name="theType"></param>
        /// <returns></returns>
        public static bool IsNullable(this Type theType)
        {
            return (theType.IsGenericType && theType.
              GetGenericTypeDefinition().Equals
              (typeof(Nullable<>)));
        }

        private static readonly MethodInfo maybeMethod;
        /// <summary>
        /// For types that return a nullable
        /// </summary>
        private static readonly MethodInfo maybeNullableMethod;

        ///// <summary>
        ///// For future version where you don't need 2 expressions on primitives?
        ///// For types that return a primitive (not nullable)
        ///// to convert them to nullable
        ///// </summary>
        //private static readonly MethodInfo maybePrimitiveMethod;

        static Walk()
        {
            Expression<Func<object>> exp = () => Walk.MaybeShallow<object, object>(null, null);
            //var name = Member.Name(exp);
            var name = "MaybeShallow";

            maybeMethod = typeof(Walk).GetMethod(name, BindingFlags.Public | BindingFlags.Static);

            Expression<Func<int?>> expNullable = () => Walk.MaybeShallowNullable<object, int?>(null, null);
            name = "MaybeShallowNullable";

            maybeNullableMethod = typeof(Walk).GetMethod(name);

            //For future version where you don't need 2 expressions on primitives?
            //maybePrimitiveMethod = typeof(Walk).GetMethod(Member.Name(() => Walk.MaybeShallowPrimitive<object, int>(null, null)));
        }

        /// <summary>
        /// For expressions that return a nullable types
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="V">Nullable<></typeparam>
        /// <param name="t"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static V MaybeShallowNullable<T, V>(this T t, Expression<Func<T, V>> expression)
            where T : class
        //			where V:struct
        {
            if (typeof(V).IsNullable() == false)
                throw new ArgumentException("MaybeShallowNullable is for nullable types");
            if (t == null)
                return default(V);
            return expression.Compile()(t);
        }

        /// <summary>
        /// For future version where you don't need 2 expressions on primitives
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="t"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static V? MaybeShallowPrimitive<T, V>(this T t, Expression<Func<T, V>> expression)
            where T : class
            where V : struct
        {
            if (typeof(V).IsNullable())
                throw new ArgumentException("MaybeShallowPrimitive is not for nullables");
            if (t != null)
                return expression.Compile()(t);
            return new Nullable<V>();
        }
        /// <summary>
        /// For struct hopping? where TCurry is not a reference type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TCurry">A type you can't check for nullable</typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="t"></param>
        /// <param name="expression"></param>
        /// <param name="finalAccessor"></param>
        /// <returns></returns>
        public static V? MaybeDeepToNullable<T, TCurry, V>(this T t, Expression<Func<T, TCurry>> expression, Func<TCurry, V> finalAccessor)
            where T : class
            where V : struct
        {
            var curry = t.MaybeDeep(expression);
            if (curry != null)
                return new Nullable<V>(finalAccessor(curry));
            return null;
        }


        /// <summary>
        /// Goes as deep as the expression so x.MaybeDeep<Z,X>( x=>x.y.z)
        /// Inspired from
        /// http://blogs.developpeur.org/miiitch/archive/2008/02/29/vendredi-c-est-expression-tree.aspx
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="t"></param>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static V MaybeDeep<T, V>(this T t, Expression<Func<T, V>> ex)
            where T : class
        {
            if (typeof(V).IsClass == false && typeof(V).IsNullable() == false)
                throw new ArgumentException("MaybeDeep does not support primitives");
            // It handles only the case of the demo
            if (ex.Body is MemberExpression)
            {
                MethodCallExpression memberEx = ConvertMemberToMethodCall(ex.Body as MemberExpression);

                LambdaExpression lambda = Expression.Lambda(memberEx, new ParameterExpression[] { ex.Parameters[0] });

                //Changed from as V to support things that are not classes
                return (V)lambda.Compile().DynamicInvoke(new object[] { t });

            }

            else if (ex.Body is NewExpression)
            {
                NewExpression newExp = (NewExpression)ex.Body;

                return ex.Compile()(t);
            }
            else
            {
                throw new NotSupportedException("");
            }

        }

        /// <summary>
        /// Only goes one level in so x.MaybeShawllow(x=>x.y)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="t"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static V MaybeShallow<T, V>(this T t, Func<T, V> selector)
            where T : class
            where V : class
        {
            if (t == null) return null;
            return selector(t);
        }

        /// <summary>
        /// This method converts a call from a member in method call. Basically:
        /// '. MaProp' becomes '. Maybe (p => p.MaProp)'
        /// </summary>
        /// <param name="memberExpression"></param>
        /// <returns></returns>
        private static MethodCallExpression ConvertMemberToMethodCall(MemberExpression memberExpression)
        {
            Expression ex = null;

            // The recursive call is made here
            if (memberExpression.Expression is MemberExpression)
            {
                ex = ConvertMemberToMethodCall(memberExpression.Expression as MemberExpression);
            }
            else
            {
                ex = memberExpression.Expression;
            }

            // A Generic method retrieves the "Maybe"
            MethodInfo methodInfo = maybeMethod;
            Type type = null;

            //default case is a property

            if (memberExpression.Member is PropertyInfo)
            {
                var prop = (PropertyInfo)memberExpression.Member;
                type = prop.PropertyType;
            }
            else
                if (memberExpression.Member is FieldInfo)
                {
                    var field = (FieldInfo)memberExpression.Member;
                    type = field.FieldType;
                }
                else
                {
                    throw new NotImplementedException("");
                }

            if (type.IsNullable())
                methodInfo = maybeNullableMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
            else if (type.IsPrimitive)
            {
                //methodInfo = maybePrimitiveMethod.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, type });
                //For future version where you don't need 2 expressions on primitives?
                throw new NotSupportedException("Primitives are not supported");
            }
            else //class
            {
                // Obligatory passage: get a version of the standard method "Maybe"
                methodInfo = methodInfo.MakeGenericMethod(
                    new Type[] { memberExpression.Member.DeclaringType, type });
            }

            // Create a parameter to the lambda passed in parameter Maybe
            ParameterExpression p = Expression.Parameter(memberExpression.Member.DeclaringType, "p");

            // Create the lambda
            LambdaExpression maybeLamba = Expression.Lambda(
                    Expression.MakeMemberAccess(p, memberExpression.Member),
                    new ParameterExpression[] { p });

            // Create the call Maybe
            MethodCallExpression result = Expression.Call(
                null, methodInfo,
                new Expression[] { ex, maybeLamba });
            return result;
        }
    }
}



